Python is something called an “object- oriented programming language.” What this means is there’s a construct in Python called a class that lets you structure your software in a particular way. Using classes, you can add consistency to your programs so that they can be used in a cleaner way, or at least that’s the theory.
Classes and objects are the two main aspects of object oriented programming. A class creates a new type where objects are instances of the class. An analogy is that you can have variables of type i n t which translates to saying that variables that store integers are variables which are instances (objects) of the int class.
Objects can store data using ordinary variables that belong to the object. Variables that belong to an object or class are referred to as fields. Objects can also have functionality by using functions that belong to a class. Such functions are called methods of the class. This terminology is important because it helps us to differentiate between functions and variables which are independent and those which belong to a class or object. Collectively, the fields and methods can be referred to as the attributes of that class.
Fields are of two types - they can belong to each instance/object of the class or they can belong to the class itself. They are called instance variables and class variables respectively.
A class is created using the class
keyword. The fields and methods of the class are listed
in an indented block.
Class methods have only one specific difference from ordinary functions - they must have an extra first name that has to be added to the beginning of the parameter list, but you do not give a value for this parameter when you call the method, Python will provide it. This particular variable refers to the object itself, and by convention, it is given the name self
.
A class is merely a container for static data members or function declarations, called a class's attributes. Classes provide something which can be considered a blueprint for creating "real" objects, called class instances. Functions which are part of classes are called methods
.
The simplest class possible is shown in the following example.
In [4]:
# Declare a Class
class Class_Name(object):
pass
class Class_Name_slow():
pass
class Class_Name_for_lazy:
pass
class Class_Name(base_classes_if_any):
"""optional documentation string"""
static_member_declarations = 1
def method_declarations(self):
"""
documentation
"""
pass
In [6]:
# first.py
class First:
pass
fr = First()
print (type(fr))
print (type(First))
print(type(int))
In [7]:
# first.py
class First(object):
pass
fr = First()
print (type(fr))
print (type(First))
print(type(int))
In [8]:
# first.py
# Class with it's methods
class Second:
def set_name(self, name):
self.fullname = name
def get_name(self):
return self.fullname
try:
sec = Second()
print(sec.get_name())
except Exception as e:
print(e)
In [9]:
# first.py
class Second:
def set_name(self, name):
print(id(self))
self.fullname = name
def get_name(self):
return self.fullname
sec = Second()
print(id(sec))
sec.set_name("Manish Gupta")
print(sec.get_name())
NOTE: both
Second
andsec
are same object as their id's are same
In [1]:
class Second:
def __init__(self, name = ""):
self.fullname = name
def set_name(self, name):
print(id(self))
self.fullname = name
def get_name(self):
return self.fullname
sec = Second("Vishal Saxena")
print(sec.get_name())
In [44]:
# first.py
class Second:
def __init__(self, name, age=35):
self.name(name)
self.age = age
def name(self, new_name):
self.fullname = new_name
def get_name(self):
return self.fullname
sec = Second("Arya")
print(sec.get_name())
print(sec.age)
In [11]:
class Second:
def __init__(self, name, age=55):
self.name(name)
self.age = age
def name(self, name):
self.name = name
def get_name(self):
return self.name
sec = Second("Rajneekanth")
print(sec.get_name())
print(sec.age)
print(dir(sec))
dir(sec.__dir__)
Out[11]:
In [18]:
# first.py
class Second:
fullname = "Mayank Johri"
age = 33
def name(self, name):
self.fullname = name
def get_name(self):
return self.fullname
sec = Second()
print(dir(sec))
print(sec.get_name())
print(id(sec.fullname))
sec2 = Second()
print(id(sec2.fullname))
In [20]:
# first.py
class Second:
fullname = "Ram Setu"
age = 33
def name(self, name):
self.fullname = name
def get_name(self):
return self.fullname
In [21]:
rs_1 = Second()
rs_2 = Second()
In [25]:
print(rs_1.fullname == rs_2.fullname)
print(id(rs_1.fullname) == id(rs_2.fullname))
In [26]:
rs_1.name("ram setu")
In [27]:
print(rs_1.fullname == rs_2.fullname)
print(id(rs_1.fullname) == id(rs_2.fullname))
In [40]:
print(rs_1.fullname, " - ", rs_2.fullname)
mutables
In [34]:
class Bridge:
fullname = ["Mayank", "Johri"]
age = 33
def name(self, name):
self.fullname.append(name)
def get_name(self):
return self.fullname
In [35]:
rs_1 = Bridge()
rs_2 = Bridge()
In [36]:
print(rs_1.fullname == rs_2.fullname)
print(id(rs_1.fullname) == id(rs_2.fullname))
In [37]:
rs_1.name("ram setu")
In [38]:
print(rs_1.fullname == rs_2.fullname)
print(id(rs_1.fullname) == id(rs_2.fullname))
In [39]:
print(rs_1.fullname, " - ", rs_2.fullname)
In [ ]:
In [37]:
# Example
class FooClass:
"""my very first class: FooClass"""
__version = 0.11 # class (data) attribute
ver = 0.1
def __init__(self, nm='John Doe'):
'constructor'
self.name = nm # class instance (data) attribute
def showName(self):
'display instance attribute and class name'
print ('Your name is: ', self.name)
print( 'My name is: ', self.__class__ )# full class name
def showVersion(self):
'display class(static) attribute'
print( self.__version )# references FooClass.version
def showVer(self):
'display class(static) attribute'
print( self.ver )# references FooClass.version
def setVersion(self, ver):
'display class(static) attribute'
self.__version = ver
print( self.__version )# references FooClass.version
# Create Class Instances
foo = FooClass()
arya = FooClass("Arya")
arya.showName()
# Calling class methods
foo.showName()
In [38]:
# print(foo.showName())
foo.showVer()
arya.showVer()
In [39]:
print(id(foo.ver))
print(id(arya.ver))
In [40]:
print(foo.ver)
foo.setVersion(10) # __version
foo.ver = 2020202 # ver
In [41]:
foo.showVer()
arya.showVer()
In [42]:
foo.name
Out[42]:
In [29]:
foo.name = "Anamika Johri"
foo.name
Out[29]:
In [30]:
foo.showVer()
print(foo.ver)
print("-"*20)
print(arya.showVer())
# print(FooClass.__version)
In [43]:
try:
print(foo.__version)
except Exception as e:
print(e)
In [8]:
# Example
class User:
"""my very first class: FooClass"""
__version = 0.11 # class (data) attribute
ver = 0.1
def __init__(self, firstname='John', surname="Doe"):
'constructor'
self.name = firstname + " " + surname
print ('Created a class instance for: ', self.name)
def showName(self):
'display instance attribute and class name'
print ('Your name is: ', self.name)
print( 'My name is: ', self.__class__ )# full class name
def showVersion(self):
'display class(static) attribute'
print( self.__version )# references FooClass.version
def showVer(self):
'display class(static) attribute'
print( self.ver )# references FooClass.version
def setVersion(self, ver):
'display class(static) attribute'
self.__version = ver
print( self.__version )# references FooClass.version
# Create Class Instances
user = User()
arya = User("Arya")
gupta = User(surname="Gupta")
print(arya.showName())
In [49]:
# Example
class User:
"""my very first class: FooClass"""
__version = 0.11 # class (data) attribute
ver = 0.1
def __init__(self, firstname, surname):
'constructor'
self.name = firstname + " " + surname
print ('Created a class instance for: ', self.name)
# full class name
def showName(self):
'display instance attribute and class name'
print ('Your name is: ', self.name)
print( 'My name is: ', self.__class__ )
def showVersion(self):
'display class(static) attribute'
print( self.__version )# references FooClass.version
def showVer(self):
'display class(static) attribute'
print( self.ver )# references FooClass.version
def setVersion(self, ver):
'display class(static) attribute'
self.__version = ver
print( self.__version )# references FooClass.version
In [51]:
# Create Class Instances
try:
user = User()
arya = User("Arya")
gupta = User(surname="Gupta")
gupta = User(surname="Gupta", firstname="Manish")
arya.showName()
except Exception as e:
print(e)
So, we can't have any object creation with lesser than two parameters. Lets comment out the first three object creation code and try again
In [52]:
# Create Class Instances
try:
# user = User()
# arya = User("Arya")
# gupta = User(surname="Gupta")
gupta = User(surname="Gupta", firstname="Manish")
arya.showName()
except Exception as e:
print(e)
In [14]:
class PrivateVariables():
__version = 1.0
_vers = 11.0
ver = 10.0
def show_version(self):
return(self.__version)
def show_vers(self):
print(self._vers)
In [15]:
pv = PrivateVariables()
print(pv.ver)
print(pv._vers)
# print(pv.__version)
pv.ver = 111
print(pv.ver)
pv._vers = 1000
print(pv._vers) # Convension only
print(pv.show_version())
In [17]:
print(dir(pv))
In [21]:
print(pv.__dict__)
In [32]:
print(pv.__dict__.get('__version', "default value"))
In [33]:
print(pv.__dict__.get('ver'))
In [35]:
pv.__dict__['ver'] = 1010
print(pv.__dict__.get('ver'))
In [39]:
try:
print(pv.__version)
except Exception as e:
print(e)
Static variables
are variables declared inside the class definition, and not inside a method are class or static variables.
But before you go all, Yahooooo... about understanding of static variables
. Please note that the implementation of static variables in python are different from Java/C++, they are unique in many ways.
Lets understand them a little using the following code
In [18]:
class Static_Test(object):
val = "Rajeev Chaturvedi"
In [19]:
s = Static_Test()
In [20]:
print(s.val,"\b,", id(s.val))
print(Static_Test.val, "\b,", id(Static_Test.val))
So far so good, val & id of val from both the instance
and class
seems to be same, thus they are pointing to same memory location which contains the value.
Now lets try to update it in class
In [22]:
Static_Test.val = "राजीव चतुर्वेदी"
In [25]:
print(s.val,"\b,", id(s.val))
print(Static_Test.val, "\b,", id(Static_Test.val))
s_new = Static_Test()
print(s_new.val,"\b,", id(s.val))
So, if we update values at class level, than they are getting reflected in all the instances as well. Now lets try to update its value in an instance and check its effect
In [27]:
s.val = "Sachin"
print(s.val,"\b,", id(s.val))
print(Static_Test.val, "\b,", id(Static_Test.val))
s_new = Static_Test()
print(s_new.val,"\b,", id(s.val))
Once, instance value has been changed then it remain changed and cannot be reverted by changing class
variable value as shown in the below code
In [29]:
Static_Test.val = "Sachin Shah"
print(s.val,"\b,", id(s.val))
print(Static_Test.val, "\b,", id(Static_Test.val))
s_new = Static_Test()
print(s_new.val,"\b,", id(s.val))
Python provides decorators @classmethod
& @staticmethod
A static
method does not receive an implicit first argument (self
or cls
). To declare a static
method decorator staticmethod
is used as shown in the below example
In [2]:
class Circle(object):
PI = 3.14
@staticmethod
def area_circle(radius):
area = 0
try:
area = PI * radius * radius
except Exception as e:
print(e)
return area
c = Circle()
print(c.area_circle(10))
As shown in the above example, static methods do not have access to any class or instance attributes. We tried to access class attribute PI
and received error message that variable not defined.
Static methods for all intent and purpose act as normal function, but are called from within an object
or class
.
Static methods similar to class methods are bound to a class
instead of its object, thus do not require a class instance creation and thus are not dependent on the state of the object.
Still there are few noticible differences between a static method and a class method, few of them are as follows:
So, if they do not have access to the class, then why are they created. We will try to understand the logic of why they should be created.
In [ ]:
Many times, we have to
In [ ]:
In [ ]:
In [ ]:
In [ ]:
In [ ]:
In [ ]:
In Python, attribute is everything, contained inside an object. In Python there is no real distinction between plain data and functions, being both objects.
The following example represents a book with a title and an author. It also provides a get_entry()
method which returns a string representation of the book.
In [43]:
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
def get_entry(self):
return f"{self.title} by {self.author}"
Every instance of this class will contain three attributes, namely title, author
, and get_entry
, in addition to the standard attributes provided by the object ancestor.
In [44]:
b = Book(title="Akme", author="Mayank")
In [45]:
print(dir(b))
In [46]:
print(b.title)
b.title = "Lets Go"
print(b.title)
print(b.get_entry())
In [4]:
data = b.get_entry
print(data)
print(data())
print(type(b.__dict__))
print(b.__dict__)
#print(b.nonExistAttribute())
In [5]:
def testtest(func):
print(func())
testtest(data)
Instead of using the normal statements to access attributes, you can use the following functions −
getattr
: to access the attribute of the object
The getattr(obj, name[, default])
: to access the attribute of object.
The hasattr(obj,name)
: to check if an attribute exists or not.
The setattr(obj,name,value)
: to set an attribute. If attribute does not exist, then it would be created.
The delattr(obj, name)
: to delete an attribute.
Sometimes you want to have an attribute whose value comes from other attributes or, in general, which value shall be computed at the moment. The standard way to deal with this situation is to create a method, called getter, just like I did with get_entry().
In Python you can "mask" the method, aliasing it with a data attribute, which in this case is called property
.
In [23]:
class Book(object):
def __init__(self, title, author):
self.title = title
self.author = author
def get_entry(self):
return "{0} by {1}".format(self.title, self.author)
entry = property(get_entry)
b = Book(title="Pawn of Prophecy", author="David Eddings")
print(b.entry)
Properties allow to specify also a write method (a setter), that is automatically called when you try to change the value of the property itself.
NOTE:
Don't Worry to much about properties, we have entire chapter dedicated for it.
In [11]:
class User():
def __init__(self, name):
self.name = name
def getname(self):
return "User's full name is: {0}".format(self.name)
def setname(self, name):
self.name = name
fullname = property(getname, setname)
user = User("Roshan Musheer")
print(user.fullname)
user.fullname = "Shaeel Parez"
print(user.fullname)
# print(x)
# print(p.name)
In [ ]:
In [16]:
class TestSetter():
def setter(self, name):
self.name = name
myname = property(fset=setter)
ts = TestSetter()
ts.myname = "Mayank"
print(ts.name)
In [28]:
class A:
def get_x(self, neg=False):
return -5 if neg else 5
x = property(get_x)
a = A()
print(a.x)
In [30]:
class Book(object):
def __init__(self, title, author):
self.__title = title
self.__author = author
def __get_entry(self):
# print("_get_entry")
return "{0} by {1}".format(self.__title, self.__author)
def __set_entry(self, value):
if " by " not in value:
raise ValueError("Entries shall be formatted as '<title> by <author>'")
self.__title, self.__author = value.split(" by ")
entry = property(__get_entry, __set_entry)
def __getattr__(self, attr):
print("Sorry attribure do not exist")
return None
In [31]:
b = Book(title="Step in C", author="Mayank Johri")
print(b.entry)
b.entry = "Lets learn C by Mayank Johri"
print("*"*20)
print(b.entry)
print("*"*20)
b.entry = "Explore Go by Mayank Johri"
print("*"*20)
print(b.entry)
b.nonExistAttribute
__new__
is called for new Class type,
As per "https://www.python.org/download/releases/2.2/descrintro/#__new__"
Here are some rules for __new__
:
__new__
is a static method. When defining it, you don't need to (but may!) use the phrase "__new__
= staticmethod(__new__
)", because this is implied by its name (it is special-cased by the class constructor).__new__
must be a class; the remaining arguments are the arguments as seen by the constructor call.__new__
method that overrides a base class's __new__
method may call that base class's __new__
method. The first argument to the base class's __new__
method call should be the class argument to the overriding __new__
method, not the base class; if you were to pass in the base class, you would get an instance of the base class.__new__
method must call its base class's __new__
method; that's the only way to create an instance of your object. The subclass __new__
can do two things to affect the resulting object: pass different arguments to the base class __new__
, and modify the resulting object after it's been created (for example to initialize essential instance variables).__new__
must return an object. There's nothing that requires that it return a new object that is an instance of its class argument, although that is the convention. If you return an existing object, the constructor call will still call its __init__
method. If you return an object of a different class, its __init__
method will be called. If you forget to return something, Python will unhelpfully return None, and your caller will probably be very confused.__new__
may return a cached reference to an existing object with the same value; this is what the int, str and tuple types do for small values. This is one of the reasons why their __init__
does nothing: cached objects would be re-initialized over and over. (The other reason is that there's nothing left for __init__
to initialize: __new__
returns a fully initialized object.)__init__
method and leave __new__
alone.__new__
and __init__
to accept the new signature. However, most built-in types ignore the arguments to the method they don't use; in particular, the immutable types (int, long, float, complex, str, unicode, and tuple) have a dummy __init__
, while the mutable types (dict, list, file, and also super, classmethod, staticmethod, and property) have a dummy __new__
. The built-in type 'object' has a dummy __new__
and a dummy __init__
(which the others inherit). The built-in type 'type' is special in many respects; see the section on metaclasses.__new__
, but is handy to know anyway.) If you subclass a built-in type, extra space is automatically added to the instances to accomodate dict and weakrefs. (The dict is not initialized until you use it though, so you shouldn't worry about the space occupied by an empty dictionary for each instance you create.) If you don't need this extra space, you can add the phrase "`__slots__
= []" to your class. (See above for more about
slots`.)__new__
is a static method, not a class method. I initially thought it would have to be a class method, and that's why I added the classmethod primitive. Unfortunately, with class methods, upcalls don't work right in this case, so I had to make it a static method with an explicit class as its first argument. Ironically, there are now no known uses for class methods in the Python distribution (other than in the test suite). I might even get rid of classmethod in a future release if no good use for it can be found!Use __new__
when you need to control the creation of a new instance. Use __init__
when you need to control initialization of a new instance.
__new__
is the first step of instance creation. It's called first, and is responsible for returning a new instance of your class. In contrast, __init__
doesn't return anything; it's only responsible for initializing the instance after it's been created.
In general, you shouldn't need to override __new__
unless you're subclassing an immutable type like str, int, unicode or tuple.
From: http://mail.python.org/pipermail/tutor/2008-April/061426.html
In [1]:
class MyTest:
def __new__(self):
print("in new")
def __init__(self):
print("in init")
mnt = MyTest()
In [2]:
class MyNewTest:
def __new__(self):
print("in new")
def __new__(self, name):
print("in new", name)
def __init__(self, name):
print("in init", name)
mnt = MyNewTest("Hari Hari")
Lets look at another example, we have removed the __new__
method from the above class and created an object.
In [53]:
class MyNewTest:
def __init__(self, name):
print("in init", name)
mnt = MyNewTest("Hari Hari")
Now lets check where its goog idea to use __init__
and where __new__
.
One thumb rule is try to avoid using __new__
and let python handle it because almost all the things you wish to do in constructor can be done in __init__
. Still if you wish to do so, below examples will show you how to do it currectly.
In the first example, we have __init__
function and are using it.
In [3]:
class MyNewTest:
def __init__(self, name):
print("in init", name)
self.name = name
def print_name(self):
print(self.name)
mnt = MyNewTest("Hari Hari")
mnt.print_name()
We saw, that everything was working without any issue. Now lets try to replace __init__
with __new__
.
In [4]:
# -----------------#
# Very Bad Example #
# -----------------#
class MyNewTest:
def __new__(cls, name):
print("in init", name)
cls.name = name
def print_name(self):
print(self.name)
try:
mnt = MyNewTest("Hari Hari")
mnt.print_name()
except Exception as e:
print(e)
Now, since we have not returned any thing in __new__
thus mnt
is null. We must have __new__
which returns the object itself. Now to over come this issue, we need to return an instance of our class
. We can do that using instance = super(<class>, cls).__new__(cls)
as shown in the below example
In [4]:
class MyNewTest(object):
def __new__(cls, name):
print("in __new__:\n\t{0}".format(name))
instance = super(MyNewTest, cls).__new__(cls)
instance.name = name
return instance
def print_name(self):
print("print_name:\n\t{0}".format(self.name))
mnt = MyNewTest("!!! Hari Om Hari Om !!!")
mnt.print_name()
ram_ram = MyNewTest("!!! Ram Ram !!!")
ram_ram.print_name()
mnt.print_name()
or, we can create the class using the following code, instance = object.__new__(cls)
. As object is parent, we are directly calling it instead of using super
.
In [5]:
class MyNewTest(object):
def __new__(cls, name):
print("in __new__", name)
instance = object.__new__(cls)
instance.name = name
print("exiting __new__", name)
return instance
# __init__ is redundent in this example.
def __init__(self, name):
print("in __init__", name)
def print_name(self):
print(self.name)
mnt = MyNewTest("Hari Hari")
mnt.print_name()
ram_ram = MyNewTest("Ram Ram")
print(ram_ram)
both super(MyNewTest, cls).__new__(cls)
and object.__new__(cls)
produce the desired instance as shown in the above examples.
If we were to return anything other than instance
of object, then __init__
function will never be called as shown in the below example.
In [3]:
class Distance(float):
def __new__(cls, dist):
print("in __new__", dist)
return dist*0.0254
# __init__ is redundent in this example,
# as it will never be called.
def __init__(self, dist):
print("in __init__", dist)
def print_dist(self):
print(self.__name__)
try:
mnt = Distance(22)
print(mnt, type(mnt))
mnt.print_dist()
except Exception as e:
print(e)
In [12]:
class Distance(float):
def __new__(cls, dist):
print("in __new__", dist)
instance = super(Distance, cls).__new__(cls)
print(type(instance))
instance.val = dist*0.0254
return instance
def __init__(self, dist):
print("in __init__", dist)
def print_dist(self):
print(self.val)
if __name__ == "__main__":
try:
mnt = Distance(22)
print(mnt, type(mnt))
mnt.print_dist()
except Exception as e:
print(e)
In singleton pattern, we create one instance of the class and all subsequent objects of that class points to the first instance.
Lets try to create a singleton class using __new__
constructor.
In [36]:
class Godlike(object):
def __new__(cls, name):
it = cls.__dict__.get("__it__")
if it is not None:
return it
cls.__it__ = it = object.__new__(cls)
it.init(name)
return it
def init(self, name):
self.name = name
def print_name(self):
print(self.name)
ohm = Godlike("Ohm")
ram = Godlike("ram")
hari = Godlike("hari")
print(ohm is ram)
print(ohm is hari)
ohm.print_name()
ram.print_name()
Note, in the above example all three objects are pointing to same object ohm
meaning all three objects are same.
Now, we might have situations where we need to raise exception, if creation of more than one instance is attempted. We can achieve it by raising an exception as shown in below example.
In [38]:
class SingletonError(Exception):
pass
class HeadMaster(object):
def __new__(cls, name):
it = cls.__dict__.get("__it__")
if it is not None:
raise SingletonError(f"Count not create new instance for value {name}")
cls.__it__ = it = object.__new__(cls)
it.__init__(name)
return it
def __init__(self, name):
self.name = name
def print_name(self):
print(self.name)
try:
print("Creating Anshu Mam as Primary School headmistress.")
anshu_mam = HeadMaster("Anshu Shrivastava")
print("Creating Rahim Sir as Primary School headmaster.")
rahim_sir = HeadMaster("Rahim Khan")
except Exception as e:
print(e)
we are going to tweak previous example and convert it to have a finite number of objects created for the class
In [1]:
class HeadMaster(object):
_instances = [] # Keep track of instance reference
limit = 2
def __new__(cls, *args, **kwargs):
if len(cls._instances) >= cls.limit:
raise RuntimeError("Creation Limit %s reached" % cls.limit)
instance = object.__init__(cls)
cls._instances.append(instance)
return instance
def __del__(self):
self._instance.remove(self)
try:
li1 = HeadMaster()
li2 = HeadMaster()
li3 = HeadMaster()
li4 = HeadMaster()
except Exception as e:
print(e)
We can customize instance object using __new__
As shown above, we can also return custom objects instead of instance of requested class as shown in one of the previous example.